package com.km.netty.server;

import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import com.km.netty.handler.NettyWebSocketServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.Future;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;

@Configuration
public class NettyWebSocketServer {

    // 定义netty启动端口
    public static final int WEB_SOCKET_PORT = 8090;
    public static final NettyWebSocketServerHandler NETTY_WEB_SOCKET_SERVER_HANDLER = new NettyWebSocketServerHandler();

    // 创建线程池执行器  处理客户端的连接请求。
    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    //NettyRuntime.availableProcessors() cpu核数  处理数据的读写和具体的业务逻辑。
    private EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());

    @Autowired
    private Environment env;


    /**
     * 启动 ws server
     *
     * @return
     * @throws InterruptedException
     */
    @PostConstruct
    @SneakyThrows
    public void start() throws InterruptedException {
        // 获取CPU核心数
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        StaticLog.info("CPU核心数: " + availableProcessors);
        // 获取JVM当前活动线程数
        int activeThreads = Thread.activeCount();
        StaticLog.info("当前活动线程数: " + activeThreads);
        // 本地主机的地址
        InetAddress inetAddress = InetAddress.getLocalHost();
        // 打印基本信息
        StaticLog.info(StrUtil.format("项目名称[spring.application.name]:{}", env.getProperty("spring.application.name")));
        StaticLog.info(StrUtil.format("项目请求地址:{}:{}", inetAddress.getHostAddress(), env.getProperty("server.port")));
        run();
        StaticLog.info("项目启动成功");
    }

    /**
     * 销毁  优雅停机
     */
    @PreDestroy
    public void destroy() {
        Future<?> future = bossGroup.shutdownGracefully();
        Future<?> future1 = workerGroup.shutdownGracefully();
        future.syncUninterruptibly();
        future1.syncUninterruptibly();
        StaticLog.info("❌关闭 ws server 成功");
    }



    public void run() throws InterruptedException {
        // 服务器启动引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)   //绑定 bossGroup（接收连接）和 workerGroup（处理请求）
                .channel(NioServerSocketChannel.class) //指定 Netty 服务器使用 NIO（非阻塞）模式
                .option(ChannelOption.SO_BACKLOG, 1024) //设置等待连接队列的大小，影响高并发性能。 (最多允许 128 个连接等待 accept() 处理。)
                .option(ChannelOption.SO_KEEPALIVE, true) //保持长连接，防止连接断开
                .handler(new LoggingHandler(LogLevel.INFO)) // 在 bossGroup 中添加日志处理器，记录 Netty 服务器启动信息
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //处理 WebSocket 数据流
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //30秒客户端没有向服务器发送心跳则关闭连接  读的空闲时间  写的空闲时间   全局  (30 秒内 未收到客户端数据，则触发 空闲检测事件（读超时）。)
                        //可用于 WebSocket 心跳检测，断开长时间不活跃的连接。
                        pipeline.addLast(new IdleStateHandler(30, 0, 0));
                        // 因为使用http协议，所以需要使用http的编码器，解码器
                        //支持 HTTP 请求的 编解码，因为 WebSocket 初始连接是基于 HTTP 的。
                        pipeline.addLast(new HttpServerCodec());
                        // 以块方式写，添加 chunkedWriter 处理器
                        pipeline.addLast(new ChunkedWriteHandler());
                        /**
                         * 说明：
                         *  1. http数据在传输过程中是分段的，HttpObjectAggregator可以把多个段聚合起来；
                         *  2. 这就是为什么当浏览器发送大量数据时，就会发出多次 http请求的原因
                         *  支持 大数据块 传输，如 WebSocket 发送大文件。
                         */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        //保存用户ip
                        //pipeline.addLast(new HttpHeadersHandler());
                        /**
                         * 说明：
                         *  1. 对于 WebSocket，它的数据是以帧frame 的形式传递的；
                         *  2. 可以看到 WebSocketFrame 下面有6个子类
                         *  3. 浏览器发送请求时： ws://localhost:7000/hello 表示请求的uri
                         *  4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议，保持长连接；
                         *      是通过一个状态码 101 来切换的
                         */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/"));
                        // 自定义handler ，处理业务逻辑   接收消息、解析消息、响应消息。
                         pipeline.addLast(NETTY_WEB_SOCKET_SERVER_HANDLER);
                    }
                });
        StaticLog.info("🚀netty同步启动成功,端口:{}",WEB_SOCKET_PORT);
        // 启动服务器，监听端口，阻塞直到启动成功
        serverBootstrap.bind(WEB_SOCKET_PORT).sync();
    }
}
